﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using Adventure.Server.Library;
using Neitzel;

namespace Adventure.Server.Data
{
    /// <summary>
    /// TextFile data adapter. Saves objects to text files.
    /// </summary>
    public class TextFileDataAdapter : IDataAdapter
    {
        #region constants

        #endregion

        #region Fields

        /// <summary>
        /// Tracesource to log everything.
        /// </summary>
        private static readonly TraceSource Logger = new TraceSource(AdventureServerConstants.TraceSourceName );

        #endregion

        #region Properties

        /// <summary>
        /// Directory to look for objects.
        /// </summary>
        public DirectoryInfo Directory { get; private set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Sets the source of the data. (Root diretory of all saved files in this case.)
        /// </summary>
        /// <param name="source">Root directory to save files in.</param>
        public void SetSource(string source)
        {
            // validate the parameters.
            if (string.IsNullOrEmpty(source)) throw new ArgumentNullException(nameof(source));
            try
            {
                Directory = new DirectoryInfo(source);
                if (!Directory.Exists)
                    Directory.Create();
            }
            catch (IOException ex)
            {
                Directory = null;
                string message = string.Format(CultureInfo.InvariantCulture,
                    Properties.Resources.DirectoryNotFoundAndCannotBeCreated, source);
                Logger.TraceEvent(TraceEventType.Critical, (int)ServerTraceId.Data, message + " {0} / {1}", ex.Message, ex.StackTrace);
                throw new ArgumentOutOfRangeException(message, ex);
            }
        }

        /// <summary>
        /// Get the object attributes of an object with the given id.
        /// </summary>
        /// <param name="id">Id of the object to get.</param>
        /// <returns>The attributes of the object or null if object does not exist.</returns>
        public ObjectAttributes GetObjectAttributes(string id)
        {
            // validate
            if (string.IsNullOrEmpty(id)) throw new ArgumentNullException(nameof(id));

            // read the file
            var attributes = new ObjectAttributes();
            var file = GetFile(id);
            if (!file.Exists) return null;
            using (var reader = new StreamReader(file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    int splitCharAt = line.FirstIndexOf(new [] { ':' });
                    var key = line.Substring(0, splitCharAt);
                    var value = DecodeValue(line.Substring(splitCharAt + 2)); // Fields are split with ": "!
                    attributes[key] = value;
                }
            }
            // Return result
            return attributes;
        }

        /// <summary>
        /// Save the object attributes to a text file.
        /// </summary>
        /// <param name="attributes">Object attributes to save to a file.</param>
        public void SaveObjectAttributes(ObjectAttributes attributes)
        {
            // validate
            if (attributes == null) throw new ArgumentNullException(nameof(attributes));

            // Get the id.
            string id = attributes[SavedObject.IdentifierAttribute];
            if (string.IsNullOrEmpty(id)) throw new ArgumentException("No identifier inside attributes.", nameof(attributes));

            // Write the file.
            var file = GetFile(id);
            using (var writer = new StreamWriter(file.Open(FileMode.Create, FileAccess.Write, FileShare.None)))
            {
                foreach (var attribute in attributes)
                {
                    writer.WriteLine("{0}: {1}", attribute.Key, EncodeValue(attribute.Value));
                }
            }
        }

        /// <summary>
        /// Delete the object with the given id.
        /// </summary>
        /// <param name="id">Id of object to delete.</param>
        public void Delete(string id)
        {
            GetFile(id)?.Delete();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Decode a value. This replaces te escaped newlines with newline characters.
        /// </summary>
        /// <param name="value">The value with newline characters.</param>
        /// <returns></returns>
        private static string DecodeValue(string value)
        {
            return value.Replace("\\n", "\n").Replace("\\r", "\r");
        }

        /// <summary>
        /// Encode a value. This replaces newline characters with the escaped values.
        /// </summary>
        /// <param name="value">The value with replaced newline characters.</param>
        /// <returns></returns>
        private static string EncodeValue(string value)
        {
            return value.Replace("\n", "\\n").Replace("\r", "\\r");
        }

        /// <summary>
        /// Get the File for the given id. This also makes sure that the directory exists.
        /// </summary>
        /// <param name="id">id of the object.</param>
        /// <returns>FileInfo of the file.</returns>
        private FileInfo GetFile(string id)
        {
            var file = new FileInfo(Directory.FullName + "/" + id + ".txt");

            if (!file.Directory.Exists)
                file.Directory.Create();

            if (!file.Directory.Exists)
                throw new IOException(string.Format(CultureInfo.InvariantCulture, "Directory cannot be created: {0}", file.Directory.FullName));

            return file;
        }
        #endregion
    }
}
